﻿#include "pch.h"
#if _QG_USE_GL
#include "qgdrv_gl_rdh.h"
#include "qgdrv_gl_cnv.h"
#include "qg_math.h"

//////////////////////////////////////////////////////////////////////////
// GL
KIMPL_SUBCLASS(GLRdh, "RenderDeviceGL");

// ES2는 현재 안드로이드랑 윈도우(ANGLES) 밖에 안됨
#if QG_OPENGL_ES2 && !_SB_ANDROID_ && !_SB_WINDOWS_
#error not support OpenGL ES2 platform. fix me!
#endif

// GLES SL용 헤더
#if QG_OPENGL_ES2
#define GLSL_PRECISION_MEDIUMP_FLOAT "precision mediump float;\n"
#else
#define GLSL_PRECISION_MEDIUMP_FLOAT ""
#endif

//
namespace _qgp
{
	// 타입
	kType RdmTypeGL()
	{
		return GLRdh::Type();
	}

	//
	struct GLVertex2D : public qbVertexOrtho
	{
		void Set(float x, float y, float u, float v, kuint cc)
		{
			pos.Set(x, y);
			coord.Set(u, v);
			color = cc;
		}

		void Set(float x, float y, const tpl::Vec2& d, kuint cc)
		{
			pos.Set(x, y);
			coord = d;
			color = cc;
		}

		void Set(const tpl::Vec3& p, float u, float v, kuint cc)
		{
			pos.Set(p.x, p.y);
			coord.Set(u, v);
			color = cc;
		}

		void Set(const tpl::Vec3& p, const tpl::Vec2& d, kuint cc)
		{
			pos.Set(p.x, p.y);
			coord = d;
			color = cc;
		}
	};

	//
	struct GLVertex3D : public qbVertexPosTexColor
	{
		void Set(const tpl::Vec3& v, const tpl::Vec2& d, kuint cc)
		{
			pos = v;
			coord = d;
			color = cc;
		}

		void Set(const tpl::Vec3& v, kuint cc)
		{
			pos = v;
			coord.Set(0.0f, 0.0f);
			color = cc;
		}
	};
}

//
GLRdh::GLRdh()
{

}

//
GLRdh::~GLRdh()
{
	//
	_Finalize();

	//
#if QG_OPENGL_ES2
	_EsTerminate();
#elif !_QG_USE_SDL
#if _SB_WINDOWS_
	if (_hrc)
	{
		if (!wglMakeCurrent(NULL, NULL) || !wglDeleteContext(_hrc))
			kobj_tracep(KLV_WARNING, KERR_CLEAN);
	}

	if (_stub)
	{
		if (_hdc)
		{
			HWND hwnd = (HWND)_stub->GetHandle();
			ReleaseDC(hwnd, _hdc);
		}

		if (_stub->GetFlags(QGSDF_FULL))
		{
			ChangeDisplaySettings(NULL, 0);
			ShowCursor(TRUE);
		}
	}
#endif
#endif
}

//
bool GLRdh::_Initialize(qgStub* stub, kint flags)
{
	// 디바이스 초기화
#if QG_OPENGL_ES2
	_EsReInit(stub);
#elif !_QG_USE_SDL
	// SDL 안쓸 때
#if _SB_WINDOWS_
	// 윈도우면
	tpl::Point size = stub->GetSize();
	GLint pfmt;

	// 전체 화면 테스트
	if (K_OFLAG(flags, QGSDF_FULL))
	{
		DEVMODE dm = {0, };

		if (!EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &dm))
		{
			// 설마 이 함수가 실패하겠냐만 서는...
			kobj_tracep(KLV_ERROR, KERR_FAIL);
			return false;
		}

		dm.dmPelsWidth = size.x;
		dm.dmPelsHeight = size.y;
		dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;

		if ((pfmt = ChangeDisplaySettings(&dm, CDS_FULLSCREEN)) != DISP_CHANGE_SUCCESSFUL)
		{
			// 전체 화면으로 바꾸지 못함
			kobj_tracep(KLV_ERROR, KERR_FAIL);
			return false;
		}
	}

	// 포맷 정보
	PIXELFORMATDESCRIPTOR pfd = {sizeof(PIXELFORMATDESCRIPTOR), 1, };

	// PFD_STEREO 스테레오 디스플레이. 즉, 3D...도 있는데 구현 귀찮...
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL;

	if (!stub->GetInsts(QGSDI_EXF_1))
		pfd.dwFlags |= PFD_DOUBLEBUFFER;

	if (K_OFLAG(flags, QGSDF_BGRA) || !K_OFLAG(flags, QGSDF_DITHER))
	{
		pfd.cColorBits = 32;
		pfd.cDepthBits = 32;
	}
	else
	{
		pfd.cColorBits = 16;
		pfd.cDepthBits = 16;
	}

	pfd.dwLayerMask = PFD_MAIN_PLANE;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cAccumBits = 0;
	pfd.cStencilBits = 1;

	// 포맷 결정
	HWND hwnd = (HWND)stub->GetHandle();
	HDC hdc = GetDC(hwnd);

	// 멀티샘플링
	bool canmsaa = false;
	int msaafmt = 0;

	if (K_OFLAG(flags, QGSDF_MSAA))
	{
		// 아래꺼 NULL. 여기서 하는게 아닌게 확실한데...
		// 하튼 현재 멀티 샘플링 안댐
		PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress("wglChoosePixelFormatARB");
		if (wglChoosePixelFormatARB)
		{
			int iAttributes[] =
			{
				WGL_DRAW_TO_WINDOW_ARB, GL_TRUE,
				WGL_SUPPORT_OPENGL_ARB, GL_TRUE,
				WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
				WGL_COLOR_BITS_ARB, 24,
				WGL_ALPHA_BITS_ARB, 8,
				WGL_DEPTH_BITS_ARB, 16,
				WGL_STENCIL_BITS_ARB, 0,
				WGL_DOUBLE_BUFFER_ARB, GL_TRUE,
				WGL_SAMPLE_BUFFERS_ARB, GL_TRUE,
				WGL_SAMPLES_ARB, 4, // <- 이 숫자가 샘플링개수
				0, 0
			};
			float fAttributes[] = {0, 0};
			int pixelfmt;
			UINT count;

			for (kint sample = 8; sample >= 2; sample -= 2)
			{
				iAttributes[19] = sample;

				if (wglChoosePixelFormatARB(hdc, iAttributes, fAttributes, 1, &pixelfmt, &count) &&
					count > 0)
				{
					canmsaa = true;
					msaafmt = pixelfmt;

					break;
				}
			}
		}
	}

	// 포맷 선택
	if (!canmsaa || !SetPixelFormat(hdc, msaafmt, &pfd))
	{
		// MSAA
		if ((pfmt = ChoosePixelFormat(hdc, &pfd)) == 0 || !SetPixelFormat(hdc, pfmt, &pfd))
		{
			// 스텐실 꺼봄
			pfd.cStencilBits = 0;
			if ((pfmt = ChoosePixelFormat(hdc, &pfd)) == 0 || !SetPixelFormat(hdc, pfmt, &pfd))
			{
				// Z버퍼 24비트
				pfd.cDepthBits = 24;
				if ((pfmt = ChoosePixelFormat(hdc, &pfd)) == 0 || !SetPixelFormat(hdc, pfmt, &pfd))
				{
					// Z버퍼 16비트
					pfd.cDepthBits = 16;
					if ((pfmt = ChoosePixelFormat(hdc, &pfd)) == 0 || !SetPixelFormat(hdc, pfmt, &pfd))
					{
						// 진행불가!!!
						// OPENGL 드라이버에 문제가 있는 듯
						kobj_tracep(KLV_ERROR, KERR_PROPERTY);
						return false;
					}
				}
			}
		}
	}

	// 컨텍스트
	HGLRC hrc;

	PFNWGLCREATECONTEXTATTRIBSARBPROC proc_create_context_attribs = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
	if (proc_create_context_attribs)
	{
		// 4.0으로 해봄
		int atcs[] =
		{
			WGL_CONTEXT_MAJOR_VERSION_ARB, 4,
			WGL_CONTEXT_MINOR_VERSION_ARB, 0,
			0
		};

		hrc = proc_create_context_attribs(hdc, 0, atcs);

		if (hrc == NULL)
		{
			// 버전 2.1 사용하면 되나?
			// -> 버쳘 박스가 2.1이더라...
			atcs[1] = 2;
			atcs[3] = 1;

			hrc = proc_create_context_attribs(hdc, 0, atcs);
		}
	}
	else
	{
		// ARB가 없거나, 실패
		hrc = wglCreateContext(hdc);
	}

	if (!hrc || !wglMakeCurrent(hdc, hrc))
	{
		kobj_tracep(KLV_ERROR, KERR_FAIL_CREATE);
		return FALSE;
	}

	// 정리
	_hdc = hdc;
	_hrc = hrc;
	_pfd = pfd;
#endif
#endif

	// 확장과 CAPS
	if (!_InitializeExtension())
		return false;

	// 오케
	kobj_traces(KLV_INFO, "OpenGL Driver");
	kobj_tracef(KLV_INFO, " - Version %.2f, GLSL version %.2f", (float)_caps.version / 100.0f, (float)_caps.version_shader / 100.0f);
	kobj_tracef(KLV_INFO, " - Driver: %s", _caps.renderer);
	kobj_tracef(KLV_INFO, " - Vendor: %s", _caps.vendor);

	// 슈퍼
	return super_type::_Initialize(stub, flags);
}

#if QG_OPENGL_ES2
//
void GLRdh::_EsTerminate() throw()
{
	if (_display != EGL_NO_DISPLAY)
	{
		eglMakeCurrent(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);

		if (_context != EGL_NO_CONTEXT)
		{
			eglDestroyContext(_display, _context);
			_context = EGL_NO_CONTEXT;
		}

		if (_surface != EGL_NO_SURFACE)
		{
			eglDestroySurface(_display, _surface);
			_surface = EGL_NO_SURFACE;
		}

		eglTerminate(_display);
		_display = EGL_NO_DISPLAY;
	}
}

//
bool GLRdh::_EsReInit(qgStub* stub) throw()
{
	// 디스플레이
#if _SB_ANDROID_
	ANativeWindow* window = (ANativeWindow*)stub->GetHandle();

	EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
#elif _SB_WINDOWS_
	PFNEGLGETPLATFORMDISPLAYEXTPROC eglGetPlatformDisplayEXT = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
	k_return_value_if_fail(eglGetPlatformDisplayEXT != NULL, false);

	const EGLint attrsDisplay[] =
	{
		// EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE
		EGL_PLATFORM_ANGLE_TYPE_ANGLE, EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE,
		EGL_NONE,
	};

	HWND hwnd = (HWND)stub->GetHandle();
	HDC hdc = GetDC(hwnd);

	EGLDisplay display = eglGetPlatformDisplayEXT(EGL_PLATFORM_ANGLE_ANGLE, hdc, attrsDisplay);
#endif

	if (display == EGL_NO_DISPLAY)
		return false;

	// 초기화 -> 버전 정보 가져오는건가부다
	EGLint verMajor, verMinor;

	if (!eglInitialize(display, &verMajor, &verMinor))
		return false;

	kobj_tracef(KLV_INFO, "ES2 Initialize version: %d.%d", verMajor, verMinor);

	// API 바인드
	eglBindAPI(EGL_OPENGL_ES_API);

	if (eglGetError() != EGL_SUCCESS)
		return false;

	// 속성 설정
#if _SB_WINDOWS_
	// 윈도우에서는 이렇게 해도 됨
	EGLint scRed = 8;
	EGLint scGreen = 8;
	EGLint scBlue = 8;
	EGLint scAlpha = 8;
	EGLint scDepth = 24;
	EGLint scStencil = 8;
#else
	// 테그라4에서 보니 이렇게 보냈드만 (8,8,8,8,16,0)으로 넘어오드만
	// 다른 놈은 모르겠음
	EGLint scRed = 5;
	EGLint scGreen = 6;
	EGLint scBlue = 5;
	EGLint scAlpha = 0;
	EGLint scDepth = 16;
	EGLint scStencil = 0;
#endif

	const EGLint attrsConfig[] =
	{
#if _SB_ANDROID_
		EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
#endif
		EGL_RED_SIZE, (scRed >= 0) ? scRed : EGL_DONT_CARE,
		EGL_GREEN_SIZE, (scGreen >= 0) ? scGreen : EGL_DONT_CARE,
		EGL_BLUE_SIZE, (scBlue >= 0) ? scBlue : EGL_DONT_CARE,
		EGL_ALPHA_SIZE, (scAlpha >= 0) ? scAlpha : EGL_DONT_CARE,
		EGL_DEPTH_SIZE, (scDepth >= 0) ? scDepth : EGL_DONT_CARE,
		EGL_STENCIL_SIZE, (scStencil >= 0) ? scStencil : EGL_DONT_CARE,
		EGL_SAMPLE_BUFFERS, 0,
		EGL_NONE
	};

	EGLint confCount;
	EGLConfig config;

	if (!eglChooseConfig(display, attrsConfig, &config, 1, &confCount) || confCount != 1)
		return false;

	eglGetConfigAttrib(display, config, EGL_RED_SIZE, &scRed);
	eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &scGreen);
	eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &scBlue);
	eglGetConfigAttrib(display, config, EGL_ALPHA_SIZE, &scAlpha);
	eglGetConfigAttrib(display, config, EGL_DEPTH_SIZE, &scDepth);
	eglGetConfigAttrib(display, config, EGL_STENCIL_SIZE, &scStencil);

	// 서피스 만들기
#if _SB_ANDROID_
	EGLint format;

	eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
	ANativeWindow_setBuffersGeometry(window, 0, 0, format);

	EGLSurface surface = eglCreateWindowSurface(display, config, window, NULL);
#elif _SB_WINDOWS_
	const EGLint attrSurface[] =
	{
		EGL_POST_SUB_BUFFER_SUPPORTED_NV, EGL_TRUE,
		EGL_NONE, EGL_NONE,
	};

	EGLSurface surface = eglCreateWindowSurface(display, config, hwnd, attrSurface);

	if (surface == EGL_NO_SURFACE)
		return false;

	if (eglGetError() != EGL_SUCCESS)
		return false;
#endif

	// 콘텍스트 만들기
	const EGLint attrContext[] =
	{
		EGL_CONTEXT_CLIENT_VERSION, 2,
		EGL_NONE
	};

	EGLContext context = eglCreateContext(display, config, NULL, attrContext);

	if (eglGetError() != EGL_SUCCESS)
		return false;

	// 오케
	eglMakeCurrent(display, surface, surface, context);

	if (eglGetError() != EGL_SUCCESS)
		return false;

	// 헐퀴
	eglSwapInterval(display, 0);

	// 저장
	_display = display;
	_surface = surface;
	_context = context;

	// 화면 크기 가져오기
#if _SB_ANDROID_
	EGLint width, height;

	eglQuerySurface(display, surface, EGL_WIDTH, &width);
	eglQuerySurface(display, surface, EGL_HEIGHT, &height);

	_prsn.t_sdm.Set(width, height);
#endif

	return true;
}
#endif

//
bool GLRdh::_InitializeExtension()
{
	// 확장 초기화
	const char* extstr = (const char*)glGetString(GL_EXTENSIONS);

	if (extstr == NULL)
		extstr = "";

	QglInitializeExtensions(extstr);

	if (!QglTestExtensions())
	{
		// 어카지... 일단 경고만
		kobj_traces(KLV_WARNING, "some of GL extensions are missing.");
	}

	// 카파
	qbRdhCaps& pc = _caps;

	// 벤더
	k_strncpy(pc.renderer, (const char*)glGetString(GL_RENDERER), 64);
	k_strncpy(pc.vendor, (const char*)glGetString(GL_VENDOR), 64);

	// 버전
	pc.version = QglGetVersion();
	pc.version_shader = QglGetShaderVersion();

	// 서포트
	pc.supp_occlusion = QglSupportOcclusion();

	// 값
	pc.max_texture_count = K_MIN(QglGetMaxTextureImageUnits(), QGSGTEX_MAX_VALUE);
	pc.max_offtex_count = QglGetMaxOffScreenCount();

	pc.max_texture_dimension = QglGetTextureDimension();

	pc.image_flag = QGRESF_BGRA;

	return true;
}

// 리셋 또는 준비
bool GLRdh::_ResetPrepare(const tpl::Point& size, bool prepare)
{
	kint i;

	// 기본값
	super_type::_ResetPrepare(size, prepare);

	// 내부 TM
	_prsn.t_otm = tpl::Mat4::OrthoLH((float)size.width, -(float)size.height, -1.0f, 1.0f);
	_prsn.t_otm.ReplaceLocation(-1.0f, 1.0f, 0.0f);
	_prsn.t_tfm = _math::BuildIrrchtTextureMatrix(0.0f, tpl::Vec2(0.0f, 0.0f), tpl::Vec2(0.0f, 1.0f), tpl::Vec2(1.0f, -1.0f));

	// 픽셀 저장 정렬
	glPixelStorei(GL_PACK_ALIGNMENT, 1);

	// 뎁스 스텐실
	glEnable(GL_DEPTH_TEST);
	glDepthMask(GL_TRUE);
	glDepthFunc(GL_LEQUAL);
	glDisable(GL_STENCIL_TEST);

	// 래스터라이저
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);

	glDisable(GL_POLYGON_OFFSET_FILL);
	//glPolygonOffset(0.0f, 0.0f);

	if (QglSupportPolygonMode())
	{
		QglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glDisable(GL_POLYGON_OFFSET_POINT);
		glDisable(GL_POLYGON_OFFSET_LINE);
		glDisable(GL_POLYGON_OFFSET_POINT);
	}

	// 블렌드
	bool alphasettle = false;

	for (i = 0; i < QglGetMaxOffScreenCount(); i++)
	{
		QglEnableIndexed(GL_BLEND, i);

		if (QglSupportSeparateAlphaBlend())
		{
			QglBlendEquationi(i, GL_FUNC_ADD);
			QglBlendFunci(i, GL_ONE, GL_ZERO);
		}
		else
		{
			if (!alphasettle)
			{
				alphasettle = true;
				glBlendEquation(GL_FUNC_ADD);
				glBlendFunc(GL_ONE, GL_ZERO);
			}
		}

		QglDisableIndexed(GL_BLEND, i);
		QglColorMaski(i, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	}

	// 텍스쳐
	for (i = 0; i < QglGetMaxTextureImageUnits(); i++)
	{
		QglActiveTexture(GL_TEXTURE0 + i);
		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		glBindTexture(GL_TEXTURE_2D, 0);
	}

	// 가위질
	glDisable(GL_SCISSOR_TEST);

	// 그 밖의 설정
	glFrontFace(GL_CW);
	QglProvokingVertex(GL_FIRST_VERTEX_CONVENTION);

#if QG_OPENGL_V3 || QG_OPENGL_V4
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
	glHint(GL_POINT_SMOOTH_HINT, GL_FASTEST);
#endif

	// 인터페이스 준비
	if (prepare)
	{
		// 색깔 출력용 텍스쳐
		_extp.tx_white = CreateTexture("gl_tex_white", 16, 16, 0, 1, QGRESF_DYNAMIC | QGTEXF_NOMIP, QGCF32_BGRA);

		qbTextureMap tm;
		_extp.tx_white->Map(&tm, QGENTF_WRITE);
		memset(tm.data, 0xFF, tm.pitch * tm.height);
		_extp.tx_white->Unmap();

		// 버퍼

		// 세이더
		static const char s_vs_ortho[] = \
			"uniform mat4 sOrthoProj;\n" \
			"attribute vec4 aposition;\n" \
			"attribute vec4 acolor;\n" \
			"varying vec2 vcoord;\n" \
			"varying vec4 vcolor;\n" \
			"void main()\n" \
			"{\n" \
			"	gl_Position = sOrthoProj * vec4(aposition.xy, 0.0, 1.0);\n" \
			"	vcoord = aposition.zw;\n" \
			"	vcolor = acolor;\n" \
			"}\n";
		static const char s_vs_proj[] = \
			"uniform mat4 sWorldViewProj;\n" \
			"attribute vec3 aposition;\n" \
			"attribute vec2 acoord;\n" \
			"attribute vec4 acolor;\n" \
			"varying vec2 vcoord;\n" \
			"varying vec4 vcolor;\n" \
			"void main()\n" \
			"{\n" \
			"	gl_Position = sWorldViewProj * vec4(aposition.xyz, 1.0);\n" \
			"	vcoord = acoord;\n" \
			"	vcolor = acolor;\n" \
			"}\n";
		static const char s_ps_tex[] = \
			GLSL_PRECISION_MEDIUMP_FLOAT \
			"uniform sampler2D sTex0;\n" \
			"varying vec2 vcoord;\n" \
			"varying vec4 vcolor;\n" \
			"void main()\n" \
			"{\n" \
			"	vec4 t = texture2D(sTex0, vcoord);\n" \
			"	vec4 c = t * vcolor;\n" \
			"	gl_FragColor = c;\n" \
			"}\n";
		static const char s_ps_glyph[] = \
			GLSL_PRECISION_MEDIUMP_FLOAT \
			"uniform sampler2D sTex0;\n" \
			"varying vec2 vcoord;\n" \
			"varying vec4 vcolor;\n" \
			"void main()\n" \
			"{\n" \
			"	float t = texture2D(sTex0, vcoord).r;\n" \
			"	float a = t * vcolor.a;\n" \
			"	gl_FragColor = a * vec4(vcolor.rgb, 1.0);\n" \
			"}\n";

		_extp.sp_ortho = CreateShader("gl_sp_ortho");
		_extp.sp_ortho->BindData(QGSHADER_VS, s_vs_ortho, sizeof(s_vs_ortho), 0);
		_extp.sp_ortho->BindData(QGSHADER_PS, s_ps_tex, sizeof(s_ps_tex), 0);
		_extp.sp_ortho->Link();

		_extp.sp_proj = CreateShader("gl_sp_proj");
		_extp.sp_proj->BindData(QGSHADER_VS, s_vs_proj, sizeof(s_vs_proj), 0);
		_extp.sp_proj->BindData(QGSHADER_PS, s_ps_tex, sizeof(s_ps_tex), 0);
		_extp.sp_proj->Link();

		_extp.sp_glyph = CreateShader("gl_sp_glyph");
		_extp.sp_glyph->BindData(QGSHADER_VS, s_vs_ortho, sizeof(s_vs_ortho), 0);
		_extp.sp_glyph->BindData(QGSHADER_PS, s_ps_glyph, sizeof(s_ps_glyph), 0);
		_extp.sp_glyph->Link();

		// 레이아웃
		static qbLayoutElement s_le_p4_color[2] =
		{
			{QGDLOU_POSITION, 0, QGDLOT_FLOAT4, 0, 0, },
			{QGDLOU_COLOR, 0, QGDLOT_COLOR, 0, 0, },
		};
		static qbLayoutElement s_le_p3_t2_color[3] =
		{
			{QGDLOU_POSITION, 0, QGDLOT_FLOAT3, 0, 0, },
			{QGDLOU_TEXTURE, 0, QGDLOT_FLOAT2, 0, 0, },
			{QGDLOU_COLOR, 0, QGDLOT_COLOR, 0, 0, },
		};
		_extp.vl_p4_color = CreateVertexLayout(K_COUNTOF(s_le_p4_color), s_le_p4_color);
		_extp.vl_p3_t2_color = CreateVertexLayout(K_COUNTOF(s_le_p3_t2_color), s_le_p3_t2_color);
	}

	// 그릴 수 있도록 함 (+뷰포트) (+씬)
	SetRenderTargets(0, NULL);

	return true;
}

//
void GLRdh::_Finalize()
{
	kint i;

	// 펜딩 오브젝트 제거
	for (i = 0; i < QGSGTEX_MAX_VALUE; i++)
	{
		kobj_unload(_pend.splr_ptrs[i]);
		kobj_unload(_pend.tex_ptrs[i]);
	}

	for (i = 0; i < QGSGOFF_MAX_VALUE; i++)
		kobj_unload(_pend.off_ptrs[i]);

	kobj_unload(_pend.shd_ptr);
	kobj_unload(_pend.los_ptr);

	kobj_unload(_pend.ib_ptr);

	for (i = 0; i < QGSGVERT_MAX_VALUE; i++)
		kobj_unload(_pend.vb_ptrs[i]);

	// 텍스쳐
	kobj_unload(_extp.tx_white);

	// 버퍼

	// 레이아웃
	kobj_unload(_extp.vl_p4_color);
	kobj_unload(_extp.vl_p3_t2_color);

	// 세이더
	kobj_unload(_extp.sp_ortho);
	kobj_unload(_extp.sp_proj);
	kobj_unload(_extp.sp_glyph);

	// PUP
	k_delete(_pend.pup_ptr_vert);
	k_delete(_pend.pup_ptr_index);

	//
	super_type::_Finalize();
}

//
bool GLRdh::Layout(kint width, kint height)
{
#if _SB_ANDROID_
	if (_stub != NULL)
	{
		_EsReInit(_stub);

		super_type::Layout(width, height);

		//
		SetViewport(0, 0, width, height, 0.0f, 1.0f);
	}
#else
	if (!super_type::Layout(width, height))
		return false;

	//
	SetViewport(0, 0, width, height, 0.0f, 1.0f);
#endif

	//
	kGuardSpinLock(_dlock)
	{
		// 이거 하는데 잠글 필요가 있을지는 모르겠음

		// 내부 TM
		_prsn.t_otm = tpl::Mat4::OrthoLH((float)width, (float)-height, -1.0f, 1.0f);
		_prsn.t_otm.ReplaceLocation(-1.0f, 1.0f, 0.0f);
	}

	return true;
	}

//
void GLRdh::Reset()
{
#if _SB_ANDROID_
	_EsTerminate();
#endif
}

//
void GLRdh::_InternalClear(kint clearance, const tpl::Color& color, kint stencil, float depth) throw()
{
	kGuardSpinLock(_dlock)
	{
		GLbitfield cf = 0;

		if (K_OFLAG(clearance, QGCLEAR_STENCIL))
		{
			if (_sion.dhsl_prop.mask_write != 0xFFFFFFFF)
			{
				glStencilMask(0xFFFFFFFF);

				_sion.dhsl_prop.mask_write = 0xFFFFFFFF;
			}

			glClearStencil(stencil);

			cf |= GL_STENCIL_BUFFER_BIT;
		}

		if (K_OFLAG(clearance, QGCLEAR_DEPTH))
		{
			if (!_sion.dhsl_prop.zwrite)
			{
				glDepthMask(GL_TRUE);

				_sion.dhsl_prop.zwrite = true;
			}

			QglClearDepth(depth);

			cf |= GL_DEPTH_BUFFER_BIT;
		}

		if (K_OFLAG(clearance, QGCLEAR_RENDER))
		{
			glClearColor(color.r, color.g, color.b, color.a);

			cf |= GL_COLOR_BUFFER_BIT;
		}

		if (cf != 0)
			glClear(cf);
	}
}

//
bool GLRdh::Enter(kint clearance)
{
	super_type::Enter(clearance);

	_InternalClear(clearance, _prsn.cr_bg, 0, 1.0f);

#if _QG_USE_SDL
	glFrontFace(GL_CW);
#endif

	return true;
}

//
bool GLRdh::Leave()
{
	return super_type::Leave();
}

//
bool GLRdh::Flush()
{
	super_type::Flush();

	glFlush();

	bool ret =
#if QG_OPENGL_ES2
		QglSwapBuffers(_display, _surface);
#elif _QG_USE_SDL
		QglSwapBuffers(NULL, NULL);
#elif _SB_WINDOWS_
		QglSwapBuffers(_hdc, NULL);
#elif _SB_APPLE_
		QglSwapBuffers(NULL, NULL);
#else
		QglSwapBuffers(_display, _drawable);
#endif

	return ret;
}

// 뷰포트
void GLRdh::SetViewport(kint x, kint y, kint w, kint h, float minz, float maxz)
{
	super_type::SetViewport(x, y, w, h, minz, maxz);

	if (w > 0 && h > 0)
	{
		_prsn.t_otm = tpl::Mat4::OrthoLH((float)w, (float)-h, -1.0f, 1.0f);
		_prsn.t_otm.ReplaceLocation(-1.0f, 1.0f, 0.0f);

		glViewport(x, y, w, h);
		QglDepthRange(minz, maxz);
	}
}

// 시자스
void GLRdh::SetScissor(bool enable, const tpl::Rect& area)
{
	if (enable)
	{
		if (!_sion.scis_enable)
		{
			glEnable(GL_SCISSOR_TEST);

			_sion.scis_enable = true;
		}

		if (_sion.scis_rect != area)
		{
			glScissor(area.left, area.top, area.Width(), area.Height());

			_sion.scis_rect = area;
		}
	}
	else
	{
		if (_sion.scis_enable)
		{
			glDisable(GL_SCISSOR_TEST);

			_sion.scis_enable = false;
		}
	}
}

// 뎁스 스텐실 상태
void GLRdh::SetDhslState(qgDhslState* dhsl)
{
	kGuardSpinLock(_dlock)
	{
		if (dhsl != NULL)
			_pend.dhsl_prop = ((GLDhslState*)dhsl)->_prop;
	}
}

// 래스터라이저 설정
void GLRdh::SetRaszState(qgRaszState* rasz)
{
	kGuardSpinLock(_dlock)
	{
		if (rasz != NULL)
			_pend.rasz_prop = ((GLRaszState*)rasz)->_prop;
	}
}

// 블렌드 상태
void GLRdh::SetBlndState(qgBlndState* blnd)
{
	kGuardSpinLock(_dlock)
	{
		if (blnd != NULL)
			memcpy(_pend.blnd_props, ((GLBlndState*)blnd)->_prop, sizeof(GLBlndProperty) * QGSGOFF_MAX_VALUE);
	}
}

// 샘플러 상태
void GLRdh::SetSplrState(kint stage, qgSplrState* splr)
{
	k_return_if_fail((ksize_t)stage < (ksize_t)QglGetMaxTextureImageUnits());

	kGuardSpinLock(_dlock)
	{
		if (splr == NULL)
		{
			if (_pend.splr_ptrs[stage] != NULL)
			{
				_pend.splr_ptrs[stage]->Unload();
				_pend.splr_ptrs[stage] = NULL;
			}
		}
		else
		{
			if (_pend.splr_ptrs[stage] != splr)
			{
				kobj_unload(_pend.splr_ptrs[stage]);
				_pend.splr_ptrs[stage] = (GLSplrState*)splr;
				splr->Load();
			}
		}
	}
}

//
void GLRdh::SetTexture(kint stage, qgTexture* tex)
{
	k_return_if_fail((ksize_t)stage < (ksize_t)QglGetMaxTextureImageUnits());

	kGuardSpinLock(_dlock)
	{

		if (tex == NULL)
		{
			if (_pend.tex_ptrs[stage] != NULL)
			{
				_pend.tex_ptrs[stage]->Unload();
				_pend.tex_ptrs[stage] = NULL;
			}
		}
		else
		{
			if (_pend.tex_ptrs[stage] != tex)
			{
				kobj_unload(_pend.tex_ptrs[stage]);
				_pend.tex_ptrs[stage] = (GLTexture*)tex;
				tex->Load();
			}
		}
	}
}

// 세이더
void GLRdh::SetShader(qgShader* shd, qgVertexLayout* lo)
{
	kGuardSpinLock(_dlock)
	{
		if (shd == NULL)
		{
			if (_pend.shd_ptr != NULL)
			{
				_pend.shd_ptr->Unload();
				_pend.shd_ptr = NULL;
			}
		}
		else
		{
			if (_pend.shd_ptr != shd)
			{
				kobj_unload(_pend.shd_ptr);
				_pend.shd_ptr = (GLShader*)shd;
				shd->Load();
			}
		}

		if (lo == NULL)
		{
			if (_pend.los_ptr != NULL)
			{
				_pend.los_ptr->Unload();
				_pend.los_ptr = NULL;
			}
		}
		else
		{
			if (_pend.los_ptr != lo)
			{
				kobj_unload(_pend.los_ptr);
				_pend.los_ptr = (GLLayout*)lo;
				lo->Load();
			}
		}
	}
}

//
void GLRdh::SetBufferVertex(kint stage, qgBuffer* buf)
{
	k_return_if_fail((ksize_t)stage < QGSGVERT_MAX_VALUE);

	kGuardSpinLock(_dlock)
	{
		if (buf == NULL || buf->GetType() != QGBUFFER_VERTEX)
		{
			if (_pend.vb_ptrs[stage] != NULL)
			{
				_pend.vb_ptrs[stage]->Unload();
				_pend.vb_ptrs[stage] = NULL;
			}
		}
		else
		{
			if (_pend.vb_ptrs[stage] != buf)
			{
				kobj_unload(_pend.vb_ptrs[stage]);
				_pend.vb_ptrs[stage] = (GLBuffer*)buf;
				buf->Load();
			}
		}
	}
}

//
void GLRdh::SetBufferIndex(qgBuffer* buf)
{
	kGuardSpinLock(_dlock)
	{
		if (buf == NULL || buf->GetType() != QGBUFFER_INDEX)
		{
			if (_pend.ib_ptr != NULL)
			{
				_pend.ib_ptr->Unload();
				_pend.ib_ptr = NULL;
			}
		}
		else
		{
			if (_pend.ib_ptr != buf)
			{
				kobj_unload(_pend.ib_ptr);
				_pend.ib_ptr = (GLBuffer*)buf;
				buf->Load();
			}
		}
	}
}

//
void GLRdh::SetRenderTargets(kint count, qgTexture** texs)
{
	// 음
}

//
void GLRdh::ClearMRTs(kint clearance, kint colorcount, const tpl::ColorValue* colors, float depth, kuint stencil)
{
	if (QglSupportMultiOffScreen())
	{
		if (K_OFLAG(clearance, QGCLEAR_RENDER))
		{
			for (GLint n = 0; n < colorcount; n++)
			{
				tpl::Color c(colors[n]);
				QglClearBufferfv(GL_COLOR, n, (const GLfloat*)c.ToPointer());
			}
		}

		// UNREAL ENGINE 4 / OSX 주의
		// AMD 그래픽 드라이버에서 DepthStencil이 동작 안한다는 보고가 있음

		switch (clearance & (QGCLEAR_DEPTH | QGCLEAR_STENCIL))
		{
			case QGCLEAR_DEPTH | QGCLEAR_STENCIL:
				QglClearBufferfi(GL_DEPTH_STENCIL, 0, 0.0f, (GLint)stencil);
				break;

			case QGCLEAR_DEPTH:
				QglClearBufferfv(GL_DEPTH, 0, &depth);
				break;

			case QGCLEAR_STENCIL:
				QglClearBufferiv(GL_STENCIL, 0, (const GLint*)&stencil);
				break;
		}
	}
	else
	{
		tpl::Color c;

		if (colorcount > 0)
			c = tpl::Color(colors[0]);
		else
			clearance &= QGCLEAR_DEPTH | QGCLEAR_STENCIL;

		_InternalClear(clearance, c, stencil, depth);
	}
}

//
void GLRdh::_InternalDraw(qgTopology tpg, kint texcount, kint vertexcount, kint vertexstart) throw()
{
	kGuardSpinLock(_dlock)
	{
		_CommitDhslState();
		_CommitRaszState();
		_CommitBlndState();
		_ProcessTextureStage(texcount);

		_CommitShader();
		_CommitDrawLayout();
		_ProcessShaderMap();

		glDrawArrays(GLConvert::Topology(tpg), vertexstart, vertexcount);
	}
}

//
void GLRdh::_InternalDrawIndexed(qgTopology tpg, kint texcount, kint indexcount, kint vertexstart) throw()
{
	kGuardSpinLock(_dlock)
	{
		CheckBindBuffer(GL_ELEMENT_ARRAY_BUFFER, (GLuint)_pend.ib_ptr->GetDescription());

		_CommitDhslState();
		_CommitRaszState();
		_CommitBlndState();
		_ProcessTextureStage(texcount);

		_CommitShader();
		_CommitDrawLayout();
		_ProcessShaderMap();

		GLenum indextype = _pend.ib_ptr->GetStride() == sizeof(kushort) ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT;

		glDrawElements(GLConvert::Topology(tpg), indexcount, indextype, NULL);
	}
}

//
void GLRdh::Draw(qgTopology tpg, kint verts)
{
	// 그리기
	_InternalDraw(tpg, QglGetMaxTextureImageUnits(), verts, 0);
}

//
void GLRdh::DrawIndexed(qgTopology tpg, kint indices)
{
	// 그리기
	_InternalDrawIndexed(tpg, QglGetMaxTextureImageUnits(), indices, 0);
}

//
void GLRdh::DrawSprite(const tpl::Rect& bound, const tpl::ColorValue& color, qgTexture* tex, const tpl::Quad* coord)
{
	k_return_if_ok(color.a < 10);

	if (coord == NULL)
		coord = (const tpl::Quad*)&_math::Global::TexCoord;

	if (tex == NULL)
		tex = _extp.tx_white;

	//
	kuint bgra = color.ToBgra();
	tpl::Quad rt = bound.ToQuad();

	//
	SetDhslState(qbStaticDhslState<false>::GetPtr());
	SetRaszState(qbStaticRaszState<QGFILL_SOLID, QGCULL_BACK>::GetPtr());
	SetBlndState(qbStaticBlndState<QGMASK_RGBA, QGBLO_ADD, QGBLF_SA, QGBLF_SA_INV, QGBLO_ADD, QGBLF_SA, QGBLF_SA_INV>::GetPtr());
	SetSplrState(0, qbStaticSplrState<>::GetPtr());
	SetTexture(0, tex);
	SetShader(_extp.sp_ortho, _extp.vl_p4_color);

	//
	_qgp::GLVertex2D* vx;
	PrimitiveBegin(QGTPG_TRI_STRIP, 4, sizeof(_qgp::GLVertex2D), (kpointer*)&vx);
	vx[0].Set(rt.left, rt.top, coord->left, coord->top, bgra);
	vx[1].Set(rt.right, rt.top, coord->right, coord->top, bgra);
	vx[2].Set(rt.left, rt.bottom, coord->left, coord->bottom, bgra);
	vx[3].Set(rt.right, rt.bottom, coord->right, coord->bottom, bgra);
	PrimitiveEnd();
}

//
void GLRdh::DrawLines(bool ortho, bool strip, kint count, const tpl::Vec3* pts, const tpl::ColorValue* colors)
{
	k_return_if_fail(count >= 2);
	k_return_if_fail(pts);

	//
	SetBlndState(qbStaticBlndState<QGMASK_RGBA, QGBLO_ADD, QGBLF_SA, QGBLF_SA_INV, QGBLO_ADD, QGBLF_SA, QGBLF_SA_INV>::GetPtr());
	SetSplrState(0, qbStaticSplrState<>::GetPtr());
	SetTexture(0, _extp.tx_white);

	//
	kint i;

	if (ortho)
	{
		// 2D
		SetDhslState(qbStaticDhslState<false>::GetPtr());
		SetRaszState(qbStaticRaszState<QGFILL_SOLID, QGCULL_BACK>::GetPtr());
		SetShader(_extp.sp_ortho, _extp.vl_p4_color);

		_qgp::GLVertex2D* v2;

		PrimitiveBegin(
			strip ? QGTPG_LINE_STRIP : QGTPG_LINE_LIST,
			count, sizeof(_qgp::GLVertex2D), (kpointer*)&v2);

		if (colors)
		{
			for (i = 0; i < count; i++)
				v2[i].Set(pts[i], 0.0f, 0.0f, colors[i].ToBgra());
		}
		else
		{
			for (i = 0; i < count; i++)
				v2[i].Set(pts[i], 0.0f, 0.0f, 0xFFFFFFFF);
		}

		PrimitiveEnd();
	}
	else
	{
		// 3D
		SetDhslState(qbStaticDhslState<true>::GetPtr());
		SetRaszState(qbStaticRaszState<QGFILL_SOLID, QGCULL_NONE>::GetPtr());
		SetShader(_extp.sp_proj, _extp.vl_p3_t2_color);

		_qgp::GLVertex3D* v3;

		PrimitiveBegin(
			strip ? QGTPG_LINE_STRIP : QGTPG_LINE_LIST,
			count, sizeof(_qgp::GLVertex3D), (kpointer*)&v3);

		if (colors)
		{
			for (i = 0; i < count; i++)
				v3[i].Set(pts[i], colors[i].ToBgra());
		}
		else
		{
			for (i = 0; i < count; i++)
				v3[i].Set(pts[i], 0xFFFFFFFF);
		}

		PrimitiveEnd();
	}
}

//
void GLRdh::DrawStrips(bool ortho, kint count, const tpl::Vec3* pts, const tpl::ColorValue* colors, qgTexture* tex, const tpl::Vec2* coords)
{
	k_return_if_fail(count >= 2);
	k_return_if_fail(pts);

	//
	SetBlndState(qbStaticBlndState<QGMASK_RGBA, QGBLO_ADD, QGBLF_SA, QGBLF_SA_INV, QGBLO_ADD, QGBLF_SA, QGBLF_SA_INV>::GetPtr());
	SetSplrState(0, qbStaticSplrState<>::GetPtr());
	SetTexture(0, tex != NULL ? tex : _extp.tx_white);

	//
	float x, y;
	kint i;

	if (ortho)
	{
		// 2D
		SetDhslState(qbStaticDhslState<false>::GetPtr());
		SetRaszState(qbStaticRaszState<QGFILL_SOLID, QGCULL_BACK>::GetPtr());
		SetShader(_extp.sp_ortho, _extp.vl_p4_color);

		tpl::Vec2 h(_prsn.t_vp.size.width / 2 - 0.5f, _prsn.t_vp.size.height / 2 - 0.5f);

		_qgp::GLVertex2D* vx;
		PrimitiveBegin(QGTPG_LINE_STRIP, count + 1, sizeof(_qgp::GLVertex2D), (kpointer*)&vx);

		if (colors)
		{
			if (coords)
			{
				for (i = 0; i < count; i++)
				{
					x = -h.width + pts[i].x;
					y = h.height - pts[i].y;
					vx[i].Set(x, y, coords[i], colors[i]);
				}
			}
			else
			{
				for (i = 0; i < count; i++)
				{
					x = -h.width + pts[i].x;
					y = h.height - pts[i].y;
					vx[i].Set(x, y, 0.0f, 0.0f, colors[i]);
				}
			}
		}
		else
		{
			if (coords)
			{
				for (i = 0; i < count; i++)
				{
					x = -h.width + pts[i].x;
					y = h.height - pts[i].y;
					vx[i].Set(x, y, coords[i], 0xFFFFFFFF);
				}
			}
			else
			{
				for (i = 0; i < count; i++)
				{
					x = -h.width + pts[i].x;
					y = h.height - pts[i].y;
					vx[i].Set(x, y, 0.0f, 0.0f, 0xFFFFFFFF);
				}
			}
		}

		PrimitiveEnd();
	}
	else
	{
		// 3D
		SetDhslState(qbStaticDhslState<true>::GetPtr());
		SetRaszState(qbStaticRaszState<QGFILL_SOLID, QGCULL_NONE>::GetPtr());
		SetShader(_extp.sp_proj, _extp.vl_p3_t2_color);

		_qgp::GLVertex3D* vx;
		PrimitiveBegin(QGTPG_LINE_STRIP, count + 1, sizeof(_qgp::GLVertex3D), (kpointer*)&vx);

		if (colors)
		{
			if (coords)
			{
				for (i = 0; i < count; i++)
					vx[i].Set(pts[i], coords[i], colors[i].ToBgra());
			}
			else
			{
				for (i = 0; i < count; i++)
					vx[i].Set(pts[i], colors[i].ToBgra());
			}
		}
		else
		{
			if (coords)
			{
				for (i = 0; i < count; i++)
					vx[i].Set(pts[i], coords[i], 0xFFFFFFFF);
			}
			else
			{
				for (i = 0; i < count; i++)
					vx[i].Set(pts[i], 0xFFFFFFFF);
			}
		}

		PrimitiveEnd();
	}
}

//
void GLRdh::PrimitiveBegin(qgTopology tpg, kint verts, kint stride, kpointer* data)
{
	if (_pend.pup_tpg != 0)
	{
		kobj_tracet(KLV_ERROR, KERR_INVAL, "cannot begin draw primitive without end.");
		return;
	}

	kuint vsize = verts * stride;

	kGuardSpinLock(_dlock)
	{
		if (vsize > _pend.pup_vert_size)
		{
			k_delete(_pend.pup_ptr_vert);

			_pend.pup_ptr_vert = k_new(vsize, kbyte);
			_pend.pup_vert_size = vsize;
		}

		_pend.pup_tpg = tpg;
		_pend.pup_vert_count = verts;
		_pend.pup_vert_stride = stride;
	}

	*data = _pend.pup_ptr_vert;
}

//
void GLRdh::PrimitiveEnd()
{
	if (_pend.pup_tpg == 0)
	{
		kobj_tracet(KLV_ERROR, KERR_INVAL, "cannot end draw primitive without begin.");
		return;
	}

	kGuardSpinLock(_dlock)
	{
		_CommitDhslState();
		_CommitRaszState();
		_CommitBlndState();
		_ProcessTextureStage(QglGetMaxTextureImageUnits());

		_CommitShader();
		_CommitDrawLayoutUp(_pend.pup_ptr_vert, _pend.pup_vert_stride);
		_ProcessShaderMap();

		GLenum mode = GLConvert::Topology((qgTopology)_pend.pup_tpg);

		glDrawArrays(mode, 0, _pend.pup_vert_count);

		_pend.pup_tpg = 0;
	}
}

//
void GLRdh::PrimitiveBeginIndexed(qgTopology tpg, kint verts, kint vertstride, kpointer* vertdata, kint indices, kint indstride, kpointer* inddata)
{

}

//
void GLRdh::PrimitiveEndIndexed()
{

}

//
void GLRdh::GlyphBegin(qgShader* shd)
{
	k_return_if_ok(_pend.pup_glyph);

	//
	SetDhslState(qbStaticDhslState<false>::GetPtr());
	SetRaszState(qbStaticRaszState<QGFILL_SOLID, QGCULL_BACK>::GetPtr());
	SetBlndState(qbStaticBlndState<QGMASK_RGBA, QGBLO_ADD, QGBLF_SA, QGBLF_SA_INV, QGBLO_ADD, QGBLF_SA, QGBLF_SA_INV>::GetPtr());
	SetSplrState(0, qbStaticSplrState<>::GetPtr());
	SetShader(shd != NULL ? shd : _extp.sp_glyph, _extp.vl_p4_color);

	//
	kGuardSpinLock(_dlock)
	{
		const kuint vsize = 4 * (kuint)sizeof(_qgp::GLVertex2D);

		if (vsize > _pend.pup_vert_size)
		{
			k_delete(_pend.pup_ptr_vert);

			_pend.pup_ptr_vert = k_new(vsize, kbyte);
			_pend.pup_vert_size = vsize;
		}

		//
		_CommitDhslState();
		_CommitRaszState();
		_CommitBlndState();
		_CommitShader();
		_CommitDrawLayoutUp(_pend.pup_ptr_vert, (kuint)sizeof(_qgp::GLVertex2D));
		_ProcessShaderMap();

		for (kint i = 1; i < QglGetMaxTextureImageUnits(); i++)
			CheckBindTexture(i, GL_NONE, 0);
	}

	//
	_pend.pup_glyph = true;
}

//
void GLRdh::GlyphDraw(kint x, kint y, kint w, kint h, const tpl::ColorValue& color, qgTexture* tex)
{
	k_return_if_fail(_pend.pup_glyph);
	k_return_if_ok(color.a < 10);
	k_return_if_fail(tex != NULL);

	//
	kuint bgra = color.ToBgra();

	float fl = (float)x;
	float ft = (float)y;
	float fr = fl + w;
	float fb = ft + h;

	//
	kGuardSpinLock(_dlock)
	{
		_CommitTexture(0, (GLTexture*)tex);
		_CommitSplrState(0, _pend.splr_ptrs[0], (GLTexture*)tex);

		_qgp::GLVertex2D* vx = (_qgp::GLVertex2D*)_pend.pup_ptr_vert;
		vx[0].Set(fl, ft, 0.0f, 0.0f, bgra);
		vx[1].Set(fr, ft, 1.0f, 0.0f, bgra);
		vx[2].Set(fl, fb, 0.0f, 1.0f, bgra);
		vx[3].Set(fr, fb, 1.0f, 1.0f, bgra);

		glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
	}
}

//
void GLRdh::GlyphEnd()
{
	k_return_if_fail(_pend.pup_glyph);

	_pend.pup_glyph = false;
}

#endif	// _QG_USE_GL
